﻿using PluginNET.error;
using PluginNET.events;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;

namespace PluginNET
{
    /// <summary>
    /// 插件管理器
    /// </summary>
    public sealed class PluginManager<T> : IDisposable where T : class
    {
        /// <summary>
        /// 存储插件的目录
        /// </summary>
        private string pluginFolder;

        /// <summary>
        /// 文件系统监视工具
        /// </summary>
        private FileSystemWatcher watcher;

        /// <summary>
        /// 插件状态变化的事件
        /// </summary>
        public event PluginEvent OnPlugin;

        /// <summary>
        /// 接口的类型
        /// </summary>
        private Type interfaceType;

        /// <summary>
        /// 初始化插件管理器
        /// </summary>
        /// <param name="pluginFolder">插件所在目录</param>
        public PluginManager(string pluginFolder = "plugin")
        {
            // 检查泛型类型
            interfaceType = typeof(T);
            if (!interfaceType.IsInterface)
            {
                throw new PluginInvalidGeneralTypeException();
            }

            // 判断传入的路径是绝对路径还是相对路径
            // 如是相对路径，那就搞成绝对路径
            this.pluginFolder = Path.IsPathRooted(pluginFolder) ? pluginFolder : Path.Combine(AppDomain.CurrentDomain.BaseDirectory, pluginFolder);

            // 如果插件目录不存在，就创建
            if (!Directory.Exists(this.pluginFolder))
            {
                Directory.CreateDirectory(this.pluginFolder);
            }
        }

        /// <summary>
        /// 加载插件目录下的所有插件
        /// </summary>
        /// <param name="filter">插件过滤器，返回false表示不加载这个插件</param>
        public void Load(Func<string, bool> filter = null)
        {
            // 遍历插件目录下的dll
            foreach (var filename in Directory.GetFiles(pluginFolder, "*.dll")
                .Where(filename => filter == null || filter(filename)))
            {
                Load(filename);
            }
        }

        /// <summary>
        /// 从文件加载程序集
        /// </summary>
        /// <param name="filename"></param>
        private void Load(string filename)
        {
            // 加载程序集
            var asm = LoadAssembly(filename);

            // 如果 asm 为 null，要么是加载失败，要么是事件调用返回了true，阻止继续执行
            if (asm == null)
            {
                return;
            }

            // 查找继承自指定接口的所有类
            var classset = asm.GetTypes()
                    .Where(t => t.GetInterface(interfaceType.Name, true) != null);

            // 插件内没有找到继续接口的类
            if (!classset.Any())
            {
                EmitPluginEvent(filename, PluginEventTypes.ClassSearching, PluginErrorTypes.ImplementionClassNotFound);
                return;
            }


            foreach (var t in classset)
            {
                // 这个类型不是类class或是abstract 或不是 public
                if (!t.IsClass || t.IsAbstract && !t.IsPublic)
                {
                    EmitPluginEvent(filename, PluginEventTypes.ClassSearching, PluginErrorTypes.IllegalClassDefinition);
                    return;
                }

                try
                {
                    // 创建实例
                    var instance = asm.CreateInstance(t.FullName) as T;
                    if (instance == null)
                    {
                        // 创建实例没有报错，但实例为null，发出事件
                        EmitPluginEvent(filename,
                            PluginEventTypes.InstanceCreated,
                            PluginErrorTypes.Unkown,
                            asm,
                            t);
                        continue;
                    }

                    // 创建实例成功，发出事件
                    EmitPluginEvent(filename,
                             PluginEventTypes.InstanceCreated,
                             PluginErrorTypes.None,
                             asm,
                             t,
                             instance);
                }
                catch (Exception ex)
                {
                    // 创建实例时发生了异常，发出事件
                    EmitPluginEvent(filename,
                               PluginEventTypes.InstanceCreated,
                               PluginErrorTypes.InstanceCreateFailed,
                               asm,
                               t, exception: ex);
                }
            }
        }

        /// <summary>
        /// 开始监视插件目录，当目录下有新的dll时，会自动加载程序集并通知应用程序
        /// </summary>
        public void Watch()
        {
            // 创建watcher实例
            if (watcher == null)
            {
                watcher = new FileSystemWatcher(pluginFolder, "*.dll");
                watcher.Created += watcherEventHandler;
                watcher.Renamed += watcherEventHandler;
            }
            // 已经在监听
            if (watcher.EnableRaisingEvents)
            {
                return;
            }

            // 开始监听
            watcher.EnableRaisingEvents = true;
        }

        /// <summary>
        /// 停止监视插件目录
        /// </summary>
        public void StopWatch()
        {
            // 在监听才停止
            if (watcher.EnableRaisingEvents)
            {
                watcher.EnableRaisingEvents = false;
            }
        }

        /// <summary>
        /// 当插件目录有新的dll时或有文件改名为.dll时的处理函数
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void watcherEventHandler(object sender, FileSystemEventArgs e)
        {
            var fi = new FileInfo(e.FullPath);
            // 检查文件是否存在
            if (!fi.Exists)
            {
                return;
            }

            // 检查文件是否可用
            while (true)
            {
                try
                {
                    fi.OpenRead().Close();
                    // 没有抛出异常，表示文件可以加载了
                    break;
                }
                catch (Exception)
                {
                    // 文件还没有准备好
                    Thread.Sleep(1000);
                }
            }

            Load(e.FullPath);
        }

        /// <summary>
        /// 加载程序集
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        private Assembly LoadAssembly(string filename)
        {
            // 存放即将加载的程序集
            Assembly asm;

            // 加载程序集
            try
            {
                // 加载程序集前发出一个事件，如果程序不想用这个文件呢？对吧
                if (EmitPluginEvent(filename, PluginEventTypes.AssemblyLoading))
                {
                    return null;
                }

                // 加载
                asm = Assembly.LoadFrom(filename);

                // 加载后再发出一个事件，如果程序现在才知道不想用这个文件呢？对吧
                if (EmitPluginEvent(filename, PluginEventTypes.AssemblyLoaded, assembly: asm))
                {
                    return null;
                }

                return asm;
            }
            catch (Exception ex)
            {
                // 啊哦  程序集加载失败了
                EmitPluginEvent(filename, PluginEventTypes.AssemblyLoaded,
                    PluginErrorTypes.InvalidManagedDllFile, exception: ex);
                return null;
            }
        }

        /// <summary>
        /// 发出插件事件的公共函数
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="eventType"></param>
        /// <param name="errorType"></param>
        /// <param name="assembly"></param>
        /// <param name="classType"></param>
        /// <param name="instance"></param>
        /// <param name="exception"></param>
        /// <returns>返回true阻止插件继续加载</returns>
        private bool EmitPluginEvent(string filename,
            PluginEventTypes eventType,
            PluginErrorTypes errorType = PluginErrorTypes.None,
            Assembly assembly = null,
            Type classType = null,
            T instance = null,
            Exception exception = null)
        {
            if (OnPlugin == null)
            {
                return true;
            }
            var arg = new PluginEventArgs()
            {
                FullName = filename,
                EventType = eventType,
                ErrorType = errorType,
                Assembly = assembly,
                ClassType = classType,
                Instance = instance,
                Exception = exception
            };

            OnPlugin.Invoke(this, arg);

            return arg.Cancel;
        }

        public void Dispose()
        {

            StopWatch();
            watcher.Dispose();

            pluginFolder = null;

            OnPlugin = null;

            interfaceType = null;
        }
    }
}
